#include "ppu.h"

PPU::PPU() {}

PPU::~PPU() {}

void PPU::reset()
{
    frame_complete = false;
    fine_x = 0x00;
    address_latch = false;
    ppu_data_buffer = 0x00;
    scanline = 0;
    cycle = 0;
    bg_next_tile_id = 0x00;
    bg_next_tile_attrib = 0x00;
    bg_next_tile_lsb = 0x00;
    bg_next_tile_msb = 0x00;
    bg_shifter_pattern_lo = 0x0000;
    bg_shifter_pattern_hi = 0x0000;
    bg_shifter_attrib_lo = 0x0000;
    bg_shifter_attrib_hi = 0x0000;
    status.reg = 0x00;
    mask.reg = 0x00;
    control.reg = 0x00;
    vram_addr.reg = 0x0000;
    tram_addr.reg = 0x0000;
    odd_frame = false;

    memset(frame_data, 0, sizeof(quint8) * 256 * 240 * 3);
    memset(tblName, 0, sizeof(quint8) * 1024 * 2);
    memset(tblPalette, 0, sizeof(quint8) * 32);
}

void PPU::ConnectCartridge(Cartridge *cartridge)
{
    this->cart = cartridge;
}

quint8 PPU::get_status()
{
    // 实际上只需要高三位即可，但在nesdev上说低5位被某些游戏使用
    quint8 data = (status.reg & 0xE0) | (ppu_data_buffer & 0x1F);

    // Clear the vertical blanking flag
    status.vertical_blank = 0;

    // Reset Loopy's Address latch flag
    address_latch = false;

    return data;
}

quint8 PPU::get_oamdata()
{
    return pOAM[oam_addr];
}

quint8 PPU::read_data()
{
    quint8 data = ppu_data_buffer;
    // update the buffer for next time
    ppu_data_buffer = ppuRead(vram_addr.reg);

    if (vram_addr.reg >= 0x3F00)
        data = ppu_data_buffer;

    vram_addr.reg += (control.increment_mode ? 32 : 1);
    return data;
}

void PPU::write_ctrl(quint8 ctrl)
{
    control.reg = ctrl;
    tram_addr.nametable_x = control.nametable_x;
    tram_addr.nametable_y = control.nametable_y;
}

void PPU::write_mask(quint8 mask)
{
    this->mask.reg = mask;
}

void PPU::write_scroll(quint8 scroll)
{
    // 地址锁存器指向低位时表示写入x
    if (address_latch == false) {
        fine_x = scroll & 0x07;
        tram_addr.coarse_x = scroll >> 3;
        address_latch = true;
    } else {
        // 高位时表示写入y
        tram_addr.fine_y = scroll & 0x07;
        tram_addr.coarse_y = scroll >> 3;
        address_latch = false;
    }
}

void PPU::write_addr(quint8 addr)
{
    if (address_latch == false) {
        // 设定高6位地址
        tram_addr.reg = (quint16)((addr & 0x3F) << 8) | (tram_addr.reg & 0x00FF);
        address_latch = true;
    } else {
        // 设定低8位地址
        tram_addr.reg = (tram_addr.reg & 0xFF00) | addr;
        vram_addr = tram_addr;
        address_latch = false;
    }
}

void PPU::write_data(quint8 data)
{
    ppuWrite(vram_addr.reg, data);
    vram_addr.reg += (control.increment_mode ? 32 : 1); // 自增（换行+32）
}

void PPU::write_oamaddr(quint8 addr)
{
    oam_addr = addr;
}

void PPU::write_oamdata(quint8 data)
{
    pOAM[oam_addr] = data;
}

quint8 PPU::ppuRead(quint16 addr)
{
    quint8 data = 0x00;
    addr &= 0x3FFF;

    if (addr >= 0x0000 && addr <= 0x1FFF) {
        data = cart->PpuRead(addr);
    } else if (addr >= 0x2000 && addr <= 0x3EFF) {
        addr &= 0x0FFF;

        switch (cart->mapper_ptr->nametable_mirror) {
        case MirrorMode::HORIZONTAL:
            if (addr >= 0x0000 && addr <= 0x03FF)
                data = tblName[0][addr & 0x03FF];
            else if (addr >= 0x0400 && addr <= 0x07FF)
                data = tblName[0][addr & 0x03FF];
            else if (addr >= 0x0800 && addr <= 0x0BFF)
                data = tblName[1][addr & 0x03FF];
            else if (addr >= 0x0C00 && addr <= 0x0FFF)
                data = tblName[1][addr & 0x03FF];
            break;

        case MirrorMode::VERTICAL:
            if (addr >= 0x0000 && addr <= 0x03FF)
                data = tblName[0][addr & 0x03FF];
            else if (addr >= 0x0400 && addr <= 0x07FF)
                data = tblName[1][addr & 0x03FF];
            else if (addr >= 0x0800 && addr <= 0x0BFF)
                data = tblName[0][addr & 0x03FF];
            else if (addr >= 0x0C00 && addr <= 0x0FFF)
                data = tblName[1][addr & 0x03FF];
            break;

        case MirrorMode::ONESCREEN_LO:
            data = tblName[0][addr & 0x03FF];
            break;

        case MirrorMode::ONESCREEN_HI:
            data = tblName[1][addr & 0x03FF];
            break;

        default:
            break;
        }
    } else if (addr >= 0x3F00 && addr <= 0x3FFF) {
        addr &= 0x001F;
        if (addr == 0x0010)
            addr = 0x0000;
        if (addr == 0x0014)
            addr = 0x0004;
        if (addr == 0x0018)
            addr = 0x0008;
        if (addr == 0x001C)
            addr = 0x000C;
        data = tblPalette[addr] & (mask.grayscale ? 0x30 : 0x3F);
    }

    return data;
}

void PPU::ppuWrite(quint16 addr, quint8 data)
{
    addr &= 0x3FFF;

    if (addr <= 0x1fff) {
        cart->PpuWrite(addr, data);
    } else if (addr >= 0x2000 && addr <= 0x3EFF) {
        addr &= 0x0FFF;
        switch (cart->mapper_ptr->nametable_mirror) {
        case MirrorMode::HORIZONTAL:
            if (addr >= 0x0000 && addr <= 0x03FF)
                tblName[0][addr & 0x03FF] = data;
            else if (addr >= 0x0400 && addr <= 0x07FF)
                tblName[0][addr & 0x03FF] = data;
            else if (addr >= 0x0800 && addr <= 0x0BFF)
                tblName[1][addr & 0x03FF] = data;
            else if (addr >= 0x0C00 && addr <= 0x0FFF)
                tblName[1][addr & 0x03FF] = data;
            break;

        case MirrorMode::VERTICAL:
            if (addr >= 0x0000 && addr <= 0x03FF)
                tblName[0][addr & 0x03FF] = data;
            else if (addr >= 0x0400 && addr <= 0x07FF)
                tblName[1][addr & 0x03FF] = data;
            else if (addr >= 0x0800 && addr <= 0x0BFF)
                tblName[0][addr & 0x03FF] = data;
            else if (addr >= 0x0C00 && addr <= 0x0FFF)
                tblName[1][addr & 0x03FF] = data;
            break;

        case MirrorMode::ONESCREEN_LO:
            tblName[0][addr & 0x03FF] = data;
            break;

        case MirrorMode::ONESCREEN_HI:
            tblName[1][addr & 0x03FF] = data;
            break;

        default:
            break;
        }
    } else if (addr >= 0x3F00 && addr <= 0x3FFF) {
        addr &= 0x001F;
        if (addr == 0x0010)
            addr = 0x0000;
        if (addr == 0x0014)
            addr = 0x0004;
        if (addr == 0x0018)
            addr = 0x0008;
        if (addr == 0x001C)
            addr = 0x000C;
        tblPalette[addr] = data;
    }
}

void PPU::IncrementScrollX()
{
    // 注意移动单位是瓦片，即8x8像素
    // 仅当渲染阶段进行修改
    if (mask.render_background || mask.render_sprites) {
        // 若coarse_x == 31则我们即将进入下一个名称表
        if (vram_addr.coarse_x == 31) {
            // 回到行首
            vram_addr.coarse_x = 0;
            // 因为名称表寻址最多4个，所以横向只有0、1两种情况
            // 翻转即可
            vram_addr.nametable_x = ~vram_addr.nametable_x;
        } else {
            // 否则仅需要移动到横向下一瓦片
            vram_addr.coarse_x++;
        }
    }
}

void PPU::IncrementScrollY()
{
    // 虽然一个名称表包含32x30个瓦片，但按照地址空间实际上可以寻址到32x32个。
    // 最后两行是属性表，并不是瓦片，所以我们需要对其进行特别处理。
    // 另外，FC每次只会向下一个像素，而不是一个瓦片，所以我们应该先修改fine_y
    // 仅当fine_y == 7时，才需要移动到下一个瓦片

    // 仅当渲染阶段进行修改
    if (mask.render_background || mask.render_sprites) {
        // If possible, just increment the fine y offset
        if (vram_addr.fine_y < 7) {
            vram_addr.fine_y++;
        } else {
            // 重置fine_y
            vram_addr.fine_y = 0;

            // 检查是否需要纵向切换名称表
            if (vram_addr.coarse_y == 29) {
                vram_addr.coarse_y = 0;
                vram_addr.nametable_y = ~vram_addr.nametable_y;
            } else if (vram_addr.coarse_y == 31) {
                // 如果指向的是属性表，那么
                vram_addr.coarse_y = 0;
            } else {
                // 非特殊情况，则移动到纵向下一瓦片
                vram_addr.coarse_y++;
            }
        }
    }
}

void PPU::TransferAddressX()
{
    // 仅当渲染阶段进行修改
    if (mask.render_background || mask.render_sprites) {
        vram_addr.nametable_x = tram_addr.nametable_x;
        vram_addr.coarse_x = tram_addr.coarse_x;
    }
}

void PPU::TransferAddressY()
{
    // 仅当渲染阶段进行修改
    if (mask.render_background || mask.render_sprites) {
        vram_addr.fine_y = tram_addr.fine_y;
        vram_addr.nametable_y = tram_addr.nametable_y;
        vram_addr.coarse_y = tram_addr.coarse_y;
    }
}

void PPU::LoadBackgroundShifters()
{
    // 每个PPU时钟周期更新一个像素点，这些shifter将存储构成一个像素所需要的二进制信息
    // 每个shifter都是16位的，其高8位存储的是当前正在绘制的8个像素，低8位存储的是即将绘制的8个像素
    // 按理说以8个像素为单位，我们只需要关注msb即可。但实际上fine_x在这里可以让我们精准
    // 定位到每个像素，因此我们实际上可以选取任何一个像素
    bg_shifter_pattern_lo = (bg_shifter_pattern_lo & 0xFF00) | bg_next_tile_lsb;
    bg_shifter_pattern_hi = (bg_shifter_pattern_hi & 0xFF00) | bg_next_tile_msb;

    // 属性信息并不是以像素为单位变更的，实际上，每8个像素都拥有相同的属性
    // 因此这里我们会把8位填充满相同的属性信息
    bg_shifter_attrib_lo = (bg_shifter_attrib_lo & 0xFF00)
                           | ((bg_next_tile_attrib & 0b01) ? 0xFF : 0x00);
    bg_shifter_attrib_hi = (bg_shifter_attrib_hi & 0xFF00)
                           | ((bg_next_tile_attrib & 0b10) ? 0xFF : 0x00);
}

void PPU::UpdateShifters()
{
    // 如果绘制了一个背景像素，则左移使该像素出队
    if (mask.render_background) {
        bg_shifter_pattern_lo <<= 1;
        bg_shifter_pattern_hi <<= 1;

        bg_shifter_attrib_lo <<= 1;
        bg_shifter_attrib_hi <<= 1;
    }

    // 仅当绘制精灵且用户可见阶段
    // 如果还没绘制，则x--，表示据需要绘制更近一个周期
    // 否则移出队
    if (mask.render_sprites && cycle >= 1 && cycle < 258) {
        for (int i = 0; i < sprite_count; i++) {
            if (spriteScanline[i].x > 0) {
                spriteScanline[i].x--;
            } else {
                sprite_shifter_pattern_lo[i] <<= 1;
                sprite_shifter_pattern_hi[i] <<= 1;
            }
        }
    }
}

void PPU::clock()
{
    // -1行是我们抽象出来的，用于预渲染
    if (scanline >= -1 && scanline < 240) {
        // 背景渲染部分=================
        if (scanline == 0 && cycle == 0 && odd_frame
            && (mask.render_background || mask.render_sprites)) {
            // 奇数帧会跳过该周期
            cycle = 1;
        }

        // 实际上应该在261条扫描线做预渲染处理
        // 但是我们抽象一个-1行，更方便处理
        if (scanline == -1 && cycle == 1) {
            // 开始新一帧的渲染，清除标志
            status.vertical_blank = 0;
            status.sprite_overflow = 0;
            status.sprite_zero_hit = 0;

            // 清空精灵Shifters
            memset(sprite_shifter_pattern_lo, 0, sizeof(quint8) * 8);
            memset(sprite_shifter_pattern_hi, 0, sizeof(quint8) * 8);
        }

        if ((cycle >= 2 && cycle < 258) || (cycle >= 321 && cycle < 338)) {
            UpdateShifters();

            switch ((cycle - 1) % 8) {
                // 0-2周期，获取名称表id
            case 0:
                // 将当前背景瓦片的图案信息与属性信息加载到shifter中
                LoadBackgroundShifters();

                // 获取下一个背景瓦片id
                // 在PPU总线中，名称表的地址从0x2000开始
                // loopy_reg的低12位指向了瓦片id（偏移量）
                bg_next_tile_id = ppuRead(0x2000 | (vram_addr.reg & 0x0FFF));
                break;

                // 2-4周期，获取属性表id
            case 2:
                // (vram_addr.nametable_y << 11) | (vram_addr.nametable_x << 10)
                // | ((vram_addr.coarse_y >> 2) << 3) | (vram_addr.coarse_x >> 2)
                // Result: YX00 00yy yxxx

                // 在一个名称表内，属性表的偏移量都是从0x03C0开始的，故而基准偏移量为0x23C0
                // 这之后我们需要和瓦片所在位置做加法（|），得到瓦片的属性表位置
                bg_next_tile_attrib = ppuRead(
                    0x23C0 | (vram_addr.nametable_y << 11) | (vram_addr.nametable_x << 10)
                    | ((vram_addr.coarse_y >> 2) << 3) | (vram_addr.coarse_x >> 2));

                // 进一步，我们需要确定调色板信息
                // 如果coarse_y % 4 < 2则我们在上部，否则在下部
                // 如果coarse_x % 4 < 2则我们在左部，否则在右部
                // 由此进行移位操作，并最终取低两位作为选中的调色板
                if (vram_addr.coarse_y & 0x02)
                    bg_next_tile_attrib >>= 4;
                if (vram_addr.coarse_x & 0x02)
                    bg_next_tile_attrib >>= 2;
                bg_next_tile_attrib &= 0x03;
                break;

                // 4-6周期，获取图案表的LSB
            case 4:
                bg_next_tile_lsb = ppuRead((control.pattern_background << 12)
                                           + ((quint16) bg_next_tile_id << 4) + (vram_addr.fine_y)
                                           + 0);

                break;

                // 6-7周期，获取图案表的MSB
            case 6:
                // 注意相比于上面由8字节的偏移
                bg_next_tile_msb = ppuRead((control.pattern_background << 12)
                                           + ((quint16) bg_next_tile_id << 4) + (vram_addr.fine_y)
                                           + 8);
                break;

                // 7-8周期，移动到下一瓦片
            case 7:
                IncrementScrollX();
                break;
            }
        }

        // 一条扫描线的最后，向下移动
        if (cycle == 256) {
            IncrementScrollY();
        }

        // 256时更新了Y，则此时的X不正确，需要将tram中的信息拷贝给vram（实际上就是重置）
        if (cycle == 257) {
            LoadBackgroundShifters();
            TransferAddressX();
        }

        // 扫描线末尾多余的两次读取
        if (cycle == 338 || cycle == 340) {
            bg_next_tile_id = ppuRead(0x2000 | (vram_addr.reg & 0x0FFF));
        }

        if (scanline == -1 && cycle >= 280 && cycle < 305) {
            // VBlank阶段结束后，重置Y
            TransferAddressY();
        }

        // 精灵部分渲染 ========================================================
        // 这里没有按照Ntsc_timing做，因为它是在给下一条扫描线做准备工作，所以没必要
        // 但是可能会影响一些游戏的运行

        // 我们直接在一个周期内做完精灵相关工作
        if (cycle == 257 && scanline >= 0) {
            // 在一条扫描线的可见阶段末尾，我们来检测下一条扫描线需要渲染的精灵
            // 清空精灵缓存
            memset(spriteScanline, 0xFF, 8 * sizeof(sObjectAttributeEntry));

            // 清空精灵计数
            sprite_count = 0;

            // 清空Shifter
            memset(sprite_shifter_pattern_lo, 0, sizeof(quint8) * 8);
            memset(sprite_shifter_pattern_hi, 0, sizeof(quint8) * 8);

            // 下面我们要开始计数新的一行需要渲染的精灵
            quint8 nOAMEntry = 0;

            // 新的一行可能没有Sprite0，故先置0
            bSpriteZeroHitPossible = false;

            while (nOAMEntry < 64 && sprite_count < 9) {
                int16_t diff = ((int16_t) scanline - (int16_t) OAM[nOAMEntry].y);

                // 差值在[0，精灵高度]的将被渲染
                if (diff >= 0 && diff < (control.sprite_size ? 16 : 8) && sprite_count < 8) {
                    // 当精灵计数还不到8时，将其加入缓存
                    if (sprite_count < 8) {
                        // 如果是精灵0，它可能会触发Sprite0_hit
                        if (nOAMEntry == 0) {
                            bSpriteZeroHitPossible = true;
                        }

                        memcpy(&spriteScanline[sprite_count],
                               &OAM[nOAMEntry],
                               sizeof(sObjectAttributeEntry));
                    }
                    sprite_count++;
                }
                nOAMEntry++;
            }

            // 如果计数器超过8，则置溢出位
            status.sprite_overflow = (sprite_count >= 8);
        }

        // 在一条扫描线的最后，我们来设置Shifter信息
        if (cycle == 340) {
            for (quint8 i = 0; i < sprite_count; i++) {
                quint8 sprite_pattern_bits_lo, sprite_pattern_bits_hi; // 暂存读取精灵的数据
                quint16 sprite_pattern_addr_lo, sprite_pattern_addr_hi; // 暂存读取精灵的地址

                // 8x8大小的精灵
                if (!control.sprite_size) {
                    // 精灵不需要纵向翻转
                    if (!(spriteScanline[i].attribute & 0x80)) {
                        sprite_pattern_addr_lo = (control.pattern_sprite
                                                  << 12) // 选择图案表（0KB或4KB偏移）
                                                 | (spriteScanline[i].id
                                                    << 4) // 哪一个瓦片（TileID x 16B偏移）
                                                 | (scanline - spriteScanline[i].y); // 哪一行(0-7)
                    } else {
                        // 精灵需要纵向翻转
                        sprite_pattern_addr_lo
                            = (control.pattern_sprite << 12) // 选择图案表（0KB或4KB偏移）
                              | (spriteScanline[i].id << 4) // 哪一个瓦片（TileID x 16B偏移）
                              | (7 - (scanline - spriteScanline[i].y)); // 哪一行(7-0)
                    }
                } else {
                    // 8x16大小的精灵
                    // 精灵不需要纵向翻转
                    if (!(spriteScanline[i].attribute & 0x80)) {
                        // 8x16相当于由两个瓦片组合而成，因此我们需要判断从哪一个瓦片读取
                        if (scanline - spriteScanline[i].y < 8) {
                            // 读取上半个瓦片
                            sprite_pattern_addr_lo = ((spriteScanline[i].id & 0x01)
                                                      << 12) // 选择图案表（0KB或4KB偏移）
                                                     | ((spriteScanline[i].id & 0xFE)
                                                        << 4) // 哪一个瓦片（TileID x 16B偏移）
                                                     | ((scanline - spriteScanline[i].y)
                                                        & 0x07); // 哪一行(0-7)
                        } else {
                            // 读取下半个瓦片
                            sprite_pattern_addr_lo = ((spriteScanline[i].id & 0x01)
                                                      << 12) // 选择图案表（0KB或4KB偏移）
                                                     | (((spriteScanline[i].id & 0xFE) + 1)
                                                        << 4) // 哪一个瓦片（TileID x 16B偏移）
                                                     | ((scanline - spriteScanline[i].y)
                                                        & 0x07); // 哪一行(0-7)
                        }
                    } else {
                        // 精灵需要纵向翻转
                        if (scanline - spriteScanline[i].y < 8) {
                            // 读取上半个瓦片
                            sprite_pattern_addr_lo = ((spriteScanline[i].id & 0x01)
                                                      << 12) // 选择图案表（0KB或4KB偏移）
                                                     | (((spriteScanline[i].id & 0xFE) + 1)
                                                        << 4) // 哪一个瓦片（TileID x 16B偏移）
                                                     | (7 - (scanline - spriteScanline[i].y)
                                                        & 0x07); // 哪一行(0-7)
                        } else {
                            // 读取下半个瓦片
                            sprite_pattern_addr_lo = ((spriteScanline[i].id & 0x01)
                                                      << 12) // 选择图案表（0KB或4KB偏移）
                                                     | ((spriteScanline[i].id & 0xFE)
                                                        << 4) // 哪一个瓦片（TileID x 16B偏移）
                                                     | (7 - (scanline - spriteScanline[i].y)
                                                        & 0x07); // 哪一行(0-7)
                        }
                    }
                }

                // TODO 以上部分也许可以用一些位运算大幅简化

                // 高位相较低位总是8字节的偏移
                sprite_pattern_addr_hi = sprite_pattern_addr_lo + 8;

                // 现在我们可以从Ppu总线中读取高低位的值了
                sprite_pattern_bits_lo = ppuRead(sprite_pattern_addr_lo);
                sprite_pattern_bits_hi = ppuRead(sprite_pattern_addr_hi);

                // 如果一个精灵需要横向翻转
                if (spriteScanline[i].attribute & 0x40) {
                    // 这个lambda函数用来翻转一个字节，从而实现图像横向翻转
                    // https://stackoverflow.com/a/2602885
                    auto flipbyte = [](quint8 b) {
                        b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
                        b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
                        b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
                        return b;
                    };

                    // 横向翻转图案
                    sprite_pattern_bits_lo = flipbyte(sprite_pattern_bits_lo);
                    sprite_pattern_bits_hi = flipbyte(sprite_pattern_bits_hi);
                }

                // 将图案载入到Shifter中，供下一条扫描线渲染
                sprite_shifter_pattern_lo[i] = sprite_pattern_bits_lo;
                sprite_shifter_pattern_hi[i] = sprite_pattern_bits_hi;
            }
        }
    }

    if (scanline == 240) {
        // Post Render Scanline - Do Nothing!
    }

    if (scanline >= 241 && scanline < 261) {
        if (scanline == 241 && cycle == 1) {
            // 可见部分渲染完毕，进入垂直消隐阶段
            status.vertical_blank = 1;

            // 向CPU发送nmi信号
            if (control.enable_nmi)
                nmi = true;
        }
    }

    // 下面将根据已有的背景、精灵信息，组合出一个像素的信息
    // Background =============================================================
    quint8 bg_pixel = 0x00;   // 背景像素值2位（可有4种颜色）
    quint8 bg_palette = 0x00; // 调色板3位（可有8个调色板，不过背景只能用前四个）

    // 仅当背景渲染开启时进行
    if (mask.render_background) {
        if (mask.render_background_left || (cycle >= 9)) {
            // 根据fine_x来从shifter中选择正确的值赋予像素
            // 0x8000是最高位(1000 0000)，根据fine_x偏移来找到具体像素的偏移量
            quint16 bit_mux = 0x8000 >> fine_x;

            // 从lsb和msb中获取像素值
            quint8 p0_pixel = (bg_shifter_pattern_lo & bit_mux) > 0;
            quint8 p1_pixel = (bg_shifter_pattern_hi & bit_mux) > 0;

            // 组合成2位的像素值
            bg_pixel = (p1_pixel << 1) | p0_pixel;

            // 获取调色信息
            quint8 bg_pal0 = (bg_shifter_attrib_lo & bit_mux) > 0;
            quint8 bg_pal1 = (bg_shifter_attrib_hi & bit_mux) > 0;
            bg_palette = (bg_pal1 << 1) | bg_pal0;
        }
    }

    // Foreground =============================================================
    quint8 fg_pixel = 0x00;    // 精灵像素值2位
    quint8 fg_palette = 0x00;  // 精灵调色板3位（精灵可用后四个）
    quint8 fg_priority = 0x00; // 渲染优先级，这决定了最终像素是精灵还是背景

    // 仅当精灵渲染开启时进行
    if (mask.render_sprites) {
        if (mask.render_sprites_left || (cycle >= 9)) {
            bSpriteZeroBeingRendered = false;

            // 精灵优先级从0-63逐级递减
            // 因此我们顺序的遍历，找到第一个非透明值
            for (quint8 i = 0; i < sprite_count; i++) {
                // x==0时，应当渲染该经理你个
                if (spriteScanline[i].x == 0) {
                    // 精灵渲染时我们只需要取最高位即可，游戏会负责处理滚动
                    // 获得像素值
                    quint8 fg_pixel_lo = (sprite_shifter_pattern_lo[i] & 0x80) > 0;
                    quint8 fg_pixel_hi = (sprite_shifter_pattern_hi[i] & 0x80) > 0;
                    fg_pixel = (fg_pixel_hi << 1) | fg_pixel_lo;

                    // 获得调色板信息（注意精灵使用的是后四个）
                    fg_palette = (spriteScanline[i].attribute & 0x03) + 0x04;

                    // 获得优先级
                    fg_priority = (spriteScanline[i].attribute & 0x20) == 0;

                    // 如果该像素不是透明的，则不再向后查找（因为顺序查找到的优先级一定最高）
                    if (fg_pixel != 0) {
                        // 如果是精灵0，则标记上精灵0正在被渲染
                        // 方便下面检测Sprite0Hit
                        if (i == 0) {
                            bSpriteZeroBeingRendered = true;
                        }

                        break;
                    }
                }
            }
        }
    }

    // 现在我们既有背景像素，又有精灵像素
    // 根据他们的优先级来判断最终渲染谁
    quint8 pixel = 0x00;   // 暂存最终输出像素
    quint8 palette = 0x00; // 暂存最终输出使用的调色板序号

    if (bg_pixel == 0 && fg_pixel == 0) {
        // 如果都是透明的
        // 则输出全局背景色
        pixel = 0x00;
        palette = 0x00;
    } else if (bg_pixel == 0 && fg_pixel > 0) {
        // 精灵不透明，背景透明
        // 输出精灵
        pixel = fg_pixel;
        palette = fg_palette;
    } else if (bg_pixel > 0 && fg_pixel == 0) {
        // 背景不透明，精灵透明
        // 输出背景
        pixel = bg_pixel;
        palette = bg_palette;
    } else if (bg_pixel > 0 && fg_pixel > 0) {
        // 两个都不透明，则需要看精灵的优先级
        // 精灵标志说明其优先级高
        if (fg_priority) {
            // 则输出精灵
            pixel = fg_pixel;
            palette = fg_palette;
        } else {
            // 否则输出背景
            pixel = bg_pixel;
            palette = bg_palette;
        }

        // Sprite Zero Hit检测
        if (bSpriteZeroHitPossible && bSpriteZeroBeingRendered) {
            // 由于Sprite0Hit是检测精灵与背景的重叠/冲突
            // 故必须二者同时启用渲染才有意义
            if (mask.render_background & mask.render_sprites) {
                // 如果开启了最左边8个像素渲染
                if (!(mask.render_background_left | mask.render_sprites_left)) {
                    // 则只让后面的像素标记Sprite0Hit
                    if (cycle >= 9 && cycle < 258) {
                        status.sprite_zero_hit = 1;
                    }
                } else {
                    // 否则就全做标记
                    if (cycle >= 1 && cycle < 258) {
                        status.sprite_zero_hit = 1;
                    }
                }
            }
        }
    }

    // Finally，我们可以将该像素值存储到帧缓存中
    int x = cycle - 1, y = scanline;
    if (x >= 0 && x < 256 && y >= 0 && y < 240) {
        quint8 palette_addr = ppuRead(0x3F00 + (palette << 2) + pixel) & 0x3F;
        frame_data[x][y][0] = RGBColorMap[palette_addr][0];
        frame_data[x][y][1] = RGBColorMap[palette_addr][1];
        frame_data[x][y][2] = RGBColorMap[palette_addr][2];
    }

    // 向后继续，循环不止
    cycle++;
    // Advanced Render
    if (mask.render_background || mask.render_sprites)
        if (cycle == 260 && scanline < 240) {
            cart->mapper_ptr->scanline();
        }

    if (cycle >= 341) {
        cycle = 0;
        scanline++;
        // 一帧结束，准备下一帧
        if (scanline >= 261) {
            scanline = -1;
            frame_complete = true;
            odd_frame = !odd_frame;
        }
    }
}

QDataStream &operator<<(QDataStream &stream, const PPU &Ppu)
{
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 1024; j++)
            stream << Ppu.tblName[i][j];

    for (int i = 0; i < 32; i++)
        stream << Ppu.tblPalette[i];

    stream << Ppu.status.reg;
    stream << Ppu.mask.reg;
    stream << Ppu.control.reg;
    stream << Ppu.vram_addr.reg;
    stream << Ppu.tram_addr.reg;

    stream << Ppu.fine_x;
    stream << Ppu.address_latch;

    stream << Ppu.ppu_data_buffer;

    stream << Ppu.scanline;
    stream << Ppu.cycle;
    stream << Ppu.odd_frame;

    stream << Ppu.bg_next_tile_id;
    stream << Ppu.bg_next_tile_attrib;

    stream << Ppu.bg_next_tile_lsb;
    stream << Ppu.bg_next_tile_msb;

    stream << Ppu.bg_shifter_pattern_lo;
    stream << Ppu.bg_shifter_pattern_hi;
    stream << Ppu.bg_shifter_attrib_lo;
    stream << Ppu.bg_shifter_attrib_hi;

    for (int i = 0; i < 64; i++) {
        stream << Ppu.OAM[i].y;
        stream << Ppu.OAM[i].id;
        stream << Ppu.OAM[i].attribute;
        stream << Ppu.OAM[i].x;
    }

    stream << Ppu.oam_addr;
    stream << Ppu.sprite_count;
    for (int i = 0; i < 8; i++) {
        stream << Ppu.spriteScanline[i].y;
        stream << Ppu.spriteScanline[i].id;
        stream << Ppu.spriteScanline[i].attribute;
        stream << Ppu.spriteScanline[i].x;

        stream << Ppu.sprite_shifter_pattern_lo[i];
        stream << Ppu.sprite_shifter_pattern_hi[i];
    }

    stream << Ppu.bSpriteZeroHitPossible;
    stream << Ppu.bSpriteZeroBeingRendered;
    stream << Ppu.nmi;

    return stream;
}

QDataStream &operator>>(QDataStream &stream, PPU &Ppu)
{
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 1024; j++)
            stream >> Ppu.tblName[i][j];

    for (int i = 0; i < 32; i++)
        stream >> Ppu.tblPalette[i];

    stream >> Ppu.status.reg;
    stream >> Ppu.mask.reg;
    stream >> Ppu.control.reg;
    stream >> Ppu.vram_addr.reg;
    stream >> Ppu.tram_addr.reg;

    stream >> Ppu.fine_x;
    stream >> Ppu.address_latch;

    stream >> Ppu.ppu_data_buffer;

    stream >> Ppu.scanline;
    stream >> Ppu.cycle;
    stream >> Ppu.odd_frame;

    stream >> Ppu.bg_next_tile_id;
    stream >> Ppu.bg_next_tile_attrib;

    stream >> Ppu.bg_next_tile_lsb;
    stream >> Ppu.bg_next_tile_msb;

    stream >> Ppu.bg_shifter_pattern_lo;
    stream >> Ppu.bg_shifter_pattern_hi;
    stream >> Ppu.bg_shifter_attrib_lo;
    stream >> Ppu.bg_shifter_attrib_hi;

    for (int i = 0; i < 64; i++) {
        stream >> Ppu.OAM[i].y;
        stream >> Ppu.OAM[i].id;
        stream >> Ppu.OAM[i].attribute;
        stream >> Ppu.OAM[i].x;
    }

    stream >> Ppu.oam_addr;
    stream >> Ppu.sprite_count;
    for (int i = 0; i < 8; i++) {
        stream >> Ppu.spriteScanline[i].y;
        stream >> Ppu.spriteScanline[i].id;
        stream >> Ppu.spriteScanline[i].attribute;
        stream >> Ppu.spriteScanline[i].x;

        stream >> Ppu.sprite_shifter_pattern_lo[i];
        stream >> Ppu.sprite_shifter_pattern_hi[i];
    }

    stream >> Ppu.bSpriteZeroHitPossible;
    stream >> Ppu.bSpriteZeroBeingRendered;
    stream >> Ppu.nmi;

    return stream;
}
